<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Angular2之组件（2016-9-22）</title>
  <link rel="icon" type="image/png" href="../think/assets/ant.png">
  <link href="../styles.css" rel="stylesheet">
  <link href="../vs.css" rel="stylesheet">
  <!-- Global site tag (gtag.js) - Google Analytics -->
  <script async src="https://www.googletagmanager.com/gtag/js?id=UA-131906119-1"></script>
  <script>
    window.dataLayer = window.dataLayer || [];
    function gtag(){dataLayer.push(arguments);}
    gtag('js', new Date());

    gtag('config', 'UA-131906119-1');
  </script>
  <script async src="../index.js"></script>
</head>
<body>
<h1>Angular2之组件</h1>
<h2>前言</h2>
<p>本文是关于Angular2的第二篇文章，介绍Angular2中的核心概念<strong>组件</strong>。本文的内容，更多的是整理组件的相关用法。所参考的内容大部分来自Angular2的官方文档，在本文最后，会给出本文参考的链接。</p>
<p>用一句话来说，组件是一块视图区域，这块区域的交互由组件进行处理，同时组件能与外界进行数据交流。说的更简单些，组件就是自定义的标签，就像HTML标签一样，可以设置组件的属性，同时也可以监听组件触发的事件，组件可以像HTML标签一样被使用。组件包含的内容有：模版、样式、组件间的交互。</p>
<h2>HTML标签</h2>
<p>为了有更好的理解，先拿我们熟悉的HTML标签来作分析。</p>
<p>HTML标签可以做什么呢？HTML标签对于浏览器来说，是一块视图区域，视图区域里的内容由标签包裹的内容决定。使用者可以设置该视图区域的属性，比如设置类名、样式等，同时该视图区域也能接收用户（浏览该网页的人）的操作，并能提醒使用者。拿稍微复杂点的<code>a</code>标签来说，可以设置的常用属性有：<code>class</code>、<code>id</code>、<code>href</code>、<code>target</code>、<code>title</code>、<code>style</code>等。<code>a</code>标签也能接收用户的点击等事件，如果使用者监听了<code>a</code>标签上的点击事件，那么当用户点击了<code>a</code>标签之后，会执行使用者添加的处理函数。如下示例代码：</p>
<pre><code class="language-html"><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/login"</span>
   <span class="hljs-attr">target</span>=<span class="hljs-string">"_blank"</span>
   <span class="hljs-attr">title</span>=<span class="hljs-string">"跳转到登录页面"</span>
   <span class="hljs-attr">style</span>=<span class="hljs-string">"color: green;"</span>

   <span class="hljs-attr">onclick</span>=<span class="hljs-string">"handler"</span>&gt;</span>
   登录
 <span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
 <span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="actionscript">
    <span class="hljs-keyword">var</span> handler = <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-params">(event)</span> </span>{
        alert(<span class="hljs-string">'点击确认跳转到登录页面'</span>);
    };
 </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
</code></pre>
<p>需要注意的是，示例代码中的<code>a</code>标签有两个特点，特点一是<code>a</code>标签可以接收使用者传入的数据，比如跳转的地址<code>href</code>、跳转的策略<code>target</code>等。特点二是<code>a</code>标签也可以给使用者传输数据，方法是调用使用者监听的处理函数<code>handler</code>，传输的数据是<code>event</code>。（写到这里，我发起了呆，在想为什么都是数据传输，不同方向却使用了不同的方式...，还木有结果）</p>
<h2>Angular2中的组件</h2>
<p>上面的HTML标签，可以类比于Angular2中的组件。HTML标签是浏览器内置的，而组件相当于开发者自定义的标签。因此，组件同样拥有HTML标签的特点，可以给组件传输数据，组件也可以提供事件共使用者监听。有没发现，其实组件就是这么的简单！</p>
<p>说了这么多，来一段Angular2中定义组件的代码：</p>
<pre><code class="language-ts"><span class="hljs-meta">@Component</span>({
    selector: <span class="hljs-string">'demo'</span>,
    template: <span class="hljs-string">`
        &lt;h1&gt;{{ title }}&lt;/h1&gt;
        &lt;div class="click-area"
             (click)="onClick"&gt;&lt;/div&gt;
    `</span>,
    styles: [<span class="hljs-string">`
        h1 { color: red }
        .click-area {
            width: 600px;
            height: 600px;
            background-color: gray;
        }
    `</span>]
})
<span class="hljs-keyword">class</span> DemoComponent {
    <span class="hljs-meta">@Input</span>() title: <span class="hljs-built_in">string</span>;
    <span class="hljs-meta">@Output</span>() clickEvent = <span class="hljs-keyword">new</span> EventEmiter&lt;<span class="hljs-built_in">string</span>&gt;();

    onClick(): <span class="hljs-built_in">void</span> {
        <span class="hljs-keyword">this</span>.clickEvent.emit(<span class="hljs-string">'click event'</span>);
    }
}
</code></pre>
<p>这么一大坨代码，对于使用者来说，其实无需关心。使用者只需要知道这段代码定义了一个新的标签<code>demo</code>，这个标签有一个<code>title</code>属性表示标签的标题，同时它有一个点击的事件，当用户点击了组件里面的<code>.click-area</code>代表的区域时会触发。这是Angular2中使用ts编程语言定义组件的具体方式，对于使用者来说，何种定义方式无关紧要。（我又在想，我一开始学习前端的时候提倡HTML代码与JS代码分离，属性设置主要在HTML代码中，而事件监听在JS代码中。但是，为什么前端组件化后，反而都放到了HTML代码中了呢？我觉得是因为有了自定义组件，每个组件包含了可控的HTML代码和JS代码。统一放到HTML代码中显的更加直观些，使用者不需要关注组件内部的实现，也就是说对外是简单的，对内是可控的。这是模块化的力量）</p>
<h2>组件相关的细节</h2>
<p>组件由组件类、模版、样式组成。类比之于人，人由血肉、骨架、衣服组成。人之间的交流，是通过某些器官接收他人传来的信息，并通过某些器官发出信息给他人。组件之间的交互主要通过数据绑定进行，接收信息的属性和发送信息的属性就像人的器官。</p>
<h3>组件类</h3>
<p>组件类是组件的血肉。组件类可以理解为模版的数据源，向模版提供数据，同时处理用户的交互。模版中使用的所有动态数据都是组件类的一个属性，同时定义了该组件的属性（接收使用者传来的数据）和事件（供使用者监听）。</p>
<ul>
<li>提供属性使用<code>Input</code>修饰符。例如：<code>@Input('inpTitle') title: string</code>，表示接收使用者传来的标题，可以修改暴露的名称。</li>
<li>提供事件使用<code>Output</code>修饰符。例如：<code>@Output('outClick') click</code>，表示当发生点击事件时，调用使用者监听的点击事件处理函数。可以修改暴露的名称。</li>
<li>在类中获取模版中的某个元素，使用<code>ViewChild</code>修饰符。例如：<code>@ViewChild('demoChild')</code>，或者<code>@ViewChild(DemoChild)</code>。参数可以是<code>#</code>号引用的标签，也可以是某个组件类名称。</li>
</ul>
<h3>模版</h3>
<p>模版是组件的骨架。模版使用组件类中的属性进行填充，并保持与组件类中对应的属性一致。在模版中，可以调用所有的HTML标签和自定义的组件。</p>
<ul>
<li>引入模版的方式：<code>@Component({template: '&lt;a class=&quot;btn&quot;&gt;Save&lt;/a&gt;'})</code>或使用<code>templateUrl</code>引入模版文件。</li>
<li>注意区分HTML标签属性（attribute）和DOM属性（ property）。模版中所有的属性绑定，都是针对DOM属性，因此文中提到的属性，除特殊说明都是指DOM属性。</li>
<li>属性绑定。<code>[property]=&quot;var&quot;</code>或<code>bind-property=&quot;var&quot;</code>，去掉中括号的数据绑定（指令和组件），只会进行一次赋值，不再更新。</li>
<li>事件绑定。<code>(event)=&quot;handler($event)&quot;</code>或<code>on-event=&quot;handler($event)</code></li>
<li>双向绑定。<code>[(ngModel)]=&quot;var&quot;</code></li>
<li>HTML属性绑定。<code>[attr.aria-label]=&quot;help&quot;</code></li>
<li>标签类名绑定。<code>[class.hide]=&quot;isHide&quot;</code></li>
<li>样式绑定。<code>[style.color]=&quot;isError ? 'red' : 'gray'&quot;</code></li>
<li><code>{{ str }}</code>和<code>[innerHTML]=&quot;str&quot;</code>，都会对<code>str</code>里的<code>script</code>标签进行转义</li>
<li>常见指令：<code>[ngClass], [ngStyle], *ngIf, *ngFor, [ngSwitch]</code></li>
<li>取对象属性时防止对象还不存在时报错：<code>obj?.a?.b?.c</code></li>
</ul>
<h3>样式</h3>
<p>样式是组件的衣服。样式使组件传达的信息展现的更加友好，更利于信息的传播。</p>
<ul>
<li>引入样式的方式：<code>@Component({styles['a { color: red }']})</code>或使用<code>styleUrls</code>引入样式文件。</li>
<li>组件样式的作用域只在组件内部起作用，并且默认不影响子组件。</li>
<li>特殊选择器：<code>:host</code>（代表组件本身），<code>:host(.active)</code>（当组件含有<code>active</code>类名时样式生效）， <code>:host-context(.theme-light)</code>（当组件的任一祖先元素含有<code>theme-light</code>类名时样式生效）。</li>
<li>在父组件中设置子组件的样式：<code>:host /deep/ a</code>或者<code>:host &gt;&gt;&gt; a</code>可以设置组件中的所有子组件中的<code>a</code>标签的样式。</li>
<li>设置样式应用的方式：<code>@Component({encapsulation: ViewEncapsulation.Emulated})</code>（使用模拟方式限制样式仅作用于组件内），还可以是<code>Native</code>（使用标准的Shadow DOM）和<code>None</code>（不做任何限制，样式作用于全局）</li>
</ul>
<p>一个组件，不同的地方使用有不同的样式。达到这个目的的方式是通过<code>:host(.one-theme)</code>，将不同的放在里面。</p>
<h3>组件间的交互</h3>
<p>组件间的交互，是通过提供属性供其他组件设置，以及通过提供事件供其他组件监听的方式。提供属性，是使用者向被使用者传递信息；提供事件，是被使用者向使用者传递信息。</p>
<ul>
<li>组件接收信息的方法：<code>@Input() inp: string</code>。如果在接收的过程中希望先对数据进行处理，可以使用<code>setter</code>的方式，另外也可以在<code>ngOnChanges</code>钩子函数中获取变化前后的值，两种方式如下所有：</li>
</ul>
<pre><code class="language-ts"><span class="hljs-comment">// setter</span>
_inp: <span class="hljs-built_in">string</span> = <span class="hljs-string">'empty'</span>;
<span class="hljs-meta">@Input</span>()
<span class="hljs-keyword">set</span> inp(inp: <span class="hljs-built_in">string</span>) {
    <span class="hljs-keyword">this</span>._inp = inp.trim() || <span class="hljs-string">'empty'</span>;
}
<span class="hljs-keyword">get</span> inp() {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>._inp;
}

<span class="hljs-comment">// ngOnChanges</span>
<span class="hljs-meta">@Input</span>() inp: <span class="hljs-built_in">string</span>;
ngOnChanges(changes: {[propKey: <span class="hljs-built_in">string</span>]: SimpleChange}) {
    <span class="hljs-keyword">if</span> (changes.inp) {
        <span class="hljs-keyword">let</span> inp = changes.inp;
        <span class="hljs-built_in">console</span>.log(inp.previousValue, inp.currentValue);
    }
}
</code></pre>
<ul>
<li>组件向外传递信息的方式：<code>@Output() myClick = new EventEmiter&lt;string&gt;()</code>，通过事件的方式，由父组件去监听相应的事件。另外，在父组件中也可以直接引用到子组件示例。比如在模版中通过<code>#child-component</code>或者在组件类中通过<code>@ViewChild(ChildComponent) childComponent</code>来引用到子组件，需要注意在哪个地方引用其作用域仅在相应的地方。</li>
<li>另外一种稍微繁琐的方式是通过服务来进行组件间的交流。因此此时父组件和子组件共享服务实例。</li>
</ul>
<p>组件间的交互方式，尽量使用<code>Input</code>和<code>Output</code>修饰符的方式，在无法满足需求的情况下再考虑其他方式。</p>


</body>
</html>
